﻿// HSS.Security.KeyContainerService.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       KeyContainerService.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.Security
{
	#region Using Directives
	using System;
	using System.Security.Cryptography;
	using HSS.Security.Cryptography;
	#endregion

	#region KeyContainerService
	/// <summary>
	/// Provides functionaity to store and retrieve Key Containers for use with encryption/decryption.
	/// </summary>
	public class KeyContainerService
	{
		#region Constants

		const int PROV_RSA_FULL = 1;

		const CspProviderFlags RSA_KEY_FLAGS_MACHINE =
			CspProviderFlags.UseMachineKeyStore |
			CspProviderFlags.UseArchivableKey; // exportable

		const CspProviderFlags RSA_KEY_FLAGS_USER =
			CspProviderFlags.UseArchivableKey; // exportable

		const CspProviderFlags RSA_KEY_FLAGS_MACHINE_NOPROMPT =
			CspProviderFlags.NoPrompt |
			CspProviderFlags.UseMachineKeyStore |
			CspProviderFlags.UseArchivableKey; // exportable

		const CspProviderFlags RSA_KEY_FLAGS_USER_NOPROMPT =
			CspProviderFlags.NoPrompt |
			CspProviderFlags.UseArchivableKey; // exportable

		#endregion

		#region Constructor
		/// <summary>
		/// Default CTOR
		/// </summary>
		/// <param name="useMachineStore">Indicate whether or not use the MachineStore versus the UserProfile</param>
		/// <param name="allowPrompt">Indicate whether or not to allow prompting</param>
		/// <remarks>
		/// The values set here are used thru out the objects life time. If you want to switch context, you 
		/// must create a new instance.
		/// </remarks>
		public KeyContainerService(bool useMachineStore, bool allowPrompt)
		{
			_useMachineStore = useMachineStore;
			_allowPrompt = allowPrompt;
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets whether or not to use the MachineStore versus the UserProfile
		/// </summary>
		public bool UseMachineStore
		{
			get { return _useMachineStore; }
		} bool _useMachineStore = false;
		/// <summary>
		/// Gets whether or not to allow prompting
		/// </summary>
		public bool AllowPrompt
		{
			get { return _allowPrompt; }
		} bool _allowPrompt = false;
		#endregion

		#region Methods
		/// <summary>
		/// Creates a new key container, initializing it with a new RSA encryption key pair.
		/// </summary>
		/// <param name="keyName">The name for the key.</param>
		/// <param name="keySizeInBits">Desired key size in bits.</param>
		/// <returns>A new RSACryptoServiceProvider</returns>
		public RSACryptoServiceProvider CreateNew(string keyName, int keySizeInBits)
		{
			if (Exists(keyName))
				throw new ArgumentException("A key container with that name already exists", "keyName");

			CspParameters cspParams = new CspParameters(PROV_RSA_FULL, null, keyName);
			cspParams.KeyNumber = (int)KeyNumber.Exchange;
			cspParams.Flags = this.GetProviderFlags();

			return new RSACryptoServiceProvider(keySizeInBits, cspParams);
		}
		/// <summary>
		/// Import from a previously exported Key
		/// </summary>
		/// <param name="keyName">The name of the key to set. Must not conflict with an existing key name.</param>
		/// <param name="blobCsp">The CspBlob that includes the Private information.</param>
		public bool ImportKey(string keyName, byte[] blobCsp)
		{
			if (Exists(keyName))
				throw new ArgumentException("A key container with that name already exists", "keyName");

			CspParameters cspParams = new CspParameters(PROV_RSA_FULL, null, keyName);
			cspParams.KeyNumber = (int)KeyNumber.Exchange;

			RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspParams);
			rsa.ImportCspBlob(blobCsp);
			rsa.PersistKeyInCsp = true;
			rsa.Dispose();

			return Exists(keyName);
		}
		/// <summary>
		/// Load a previously saved Key
		/// </summary>
		/// <param name="keyName">The name of the key to fetch.</param>
		/// <returns>The restored RSACryptoServiceProvider</returns>
		public RSACryptoServiceProvider LoadKey(string keyName)
		{
			if (!Exists(keyName))
				throw new ArgumentException(string.Format("A key container with the name {0} doesn't exist, or I don't have access permissions to it", keyName), "keyName");

			CspParameters cspParams = new CspParameters(PROV_RSA_FULL, null, keyName);
			cspParams.KeyNumber = (int)KeyNumber.Exchange;
			cspParams.Flags = this.GetProviderFlags();
			return new RSACryptoServiceProvider(cspParams);
		}
		/// <summary>
		/// Deletes a saved key
		/// </summary>
		/// <param name="keyName">The name of the key to delete.</param>
		public void Delete(string keyName)
		{
			Win32Crypto.DeleteKeyContainer(keyName, this._useMachineStore, !_allowPrompt);
		}
		/// <summary>
		/// Check to see if a key exists.
		/// </summary>
		/// <param name="keyName">The name of the key to check.</param>
		/// <returns>true if the Key exists; otherwise false</returns>
		public bool Exists(string keyName)
		{
			return Win32Crypto.KeyContainerExists(keyName, this._useMachineStore, !_allowPrompt);
		}
		/// <summary>
		/// Gets a list of all saved keys.
		/// </summary>
		/// <returns>A string array contains the names of all the saved keys</returns>
		public string[] GetAllKeys()
		{
			return Win32Crypto.EnumerateKeyContainers(this._useMachineStore, !_allowPrompt);
		}

		private CspProviderFlags GetProviderFlags()
		{
			if (_allowPrompt)
			{
				if (_useMachineStore)
					return RSA_KEY_FLAGS_MACHINE;
				else
					return RSA_KEY_FLAGS_USER;
			}
			else
			{
				if (_useMachineStore)
					return RSA_KEY_FLAGS_MACHINE_NOPROMPT;
				else
					return RSA_KEY_FLAGS_USER_NOPROMPT;
			}
		}
		#endregion
	}
	#endregion
}